// Test configuration parameter tlsClusterAuthX509ExtensionValue
// aka: net.tls.clusterAuthX509.extensionValue
// @tags: [ requires_fcv_70 ]

import {determineSSLProvider} from "jstests/ssl/libs/ssl_helpers.js";

if (determineSSLProvider() !== "openssl") {
    jsTest.log('Test requires openssl based TLS support');
    quit();
}

// Fails when used without clusterAuthMode == 'X509'
{
    const opts = {auth: '', tlsClusterAuthX509ExtensionValue: 'foo'};
    const errmsg =
        'net.tls.clusterAuthX509.extensionValue requires a clusterAuthMode which allows for usage of X509';

    jsTest.log('No clusterAuthMode set');
    clearRawMongoProgramOutput();
    assert.throws(() => MongoRunner.runMongod(opts));
    assert(rawMongoProgramOutput(".*").includes(errmsg));

    jsTest.log('clusterAuthMode == keyFile');
    clearRawMongoProgramOutput();
    opts.clusterAuthMode = 'keyFile';
    assert.throws(() => MongoRunner.runMongod(opts));
    assert(rawMongoProgramOutput(".*").includes(errmsg));
}

function authAndDo(port, cert, cmd = ';') {
    jsTest.log('Connecting to localhost using cert: ' + cert);
    function x509auth(db) {
        const ext = db.getSiblingDB('$external');
        assert.commandWorked(ext.runCommand({authenticate: 1, mechanism: 'MONGODB-X509'}));
        return ext.adminCommand({connectionStatus: 1});
    }
    clearRawMongoProgramOutput();
    const shell = runMongoProgram('mongo',
                                  '--host',
                                  'localhost',
                                  '--port',
                                  port,
                                  '--tls',
                                  '--tlsCAFile',
                                  'jstests/libs/ca.pem',
                                  '--tlsCertificateKeyFile',
                                  cert,
                                  '--eval',
                                  x509auth + ' x509auth(db); ' + cmd);
    assert.eq(shell, 0);
}

function runTest(conn) {
    const SERVER_RDN = 'CN=server,OU=Kernel,O=MongoDB,L=New York City,ST=New York,C=US';
    const SERVER = 'jstests/libs/server.pem';
    const FOO_MEMBER = 'jstests/ssl/libs/cluster-member-foo.pem';
    const BAR_MEMBER = 'jstests/ssl/libs/cluster-member-bar.pem';
    const FOO_MEMBER_ALT = 'jstests/ssl/libs/cluster-member-foo-alt-rdn.pem';
    const FOO_MEMBER_ALT_RDN = 'CN=Doer,OU=Business,O=Company,L=Fakesville,ST=Example,C=ZZ';

    const admin = conn.getDB('admin');
    const ext = conn.getDB('$external');

    // Ensure no localhost auth bypass available.
    assert.commandWorked(admin.runCommand({createUser: 'admin', pwd: 'admin', roles: ['root']}));
    assert(admin.auth('admin', 'admin'));

    // Connect using server.pem which has the same RDN, but no custom extension.
    // This will result in an unknown user condition because we are
    // not recognized as a cluster member.
    assert.throws(() => authAndDo(conn.port, SERVER));

    const insertCmd = 'assert.writeOK(db.getSiblingDB("test").mycoll.insert({x:1}));';
    // Connect using same RDN WITH custom extension.
    authAndDo(conn.port, FOO_MEMBER, insertCmd);

    // Connect using cert with membership extension, but wrong value.
    assert.throws(() => authAndDo(conn.port, BAR_MEMBER));

    // Connect using cert with right membership, but different RDN (allowed).
    authAndDo(conn.port, FOO_MEMBER_ALT, insertCmd);

    // Create a user who would have been a cluster member under name based rules.
    // We should have basic privs, testing with read but not write.
    const readCmd = 'db.getSiblingDB("test").mycoll.find({});';
    const readRoles = [{db: 'admin', role: 'readAnyDatabase'}];
    assert.commandWorked(ext.runCommand({createUser: SERVER_RDN, roles: readRoles}));
    authAndDo(conn.port, SERVER, readCmd);
    assert.throws(() => authAndDo(conn.port, SERVER, insertCmd));

    // Create a user with FOO_MEMBER_ALT's RDN to validate enforceUserClusterSeparation.
    authAndDo(conn.port, FOO_MEMBER_ALT);
    assert.commandWorked(ext.runCommand({createUser: FOO_MEMBER_ALT_RDN, roles: readRoles}));
    assert.throws(() => authAndDo(conn.port, FOO_MEMBER_ALT));
}

{
    const opts = {
        auth: '',
        tlsMode: 'requireTLS',
        tlsCertificateKeyFile: 'jstests/ssl/libs/cluster-member-foo.pem',
        tlsCAFile: 'jstests/libs/ca.pem',
        clusterAuthMode: 'x509',
        tlsClusterAuthX509ExtensionValue: 'foo',
        setParameter: {
            enforceUserClusterSeparation: 'true',
        },
    };

    const mongod = MongoRunner.runMongod(opts);
    runTest(mongod);
    MongoRunner.stopMongod(mongod);
}